package lv.solcraft.replication.client;

import java.util.List;

import lv.solcraft.replication.client.filtering.OperFilter;
import lv.solcraft.replication.client.http.ServerPing;
import lv.solcraft.replication.client.logging.ClientLogger;
import lv.solcraft.replication.common.OperDTO;
import lv.solcraft.replication.common.server.SendingFacade;
import lv.solcraft.replication.common.util.ShutdownEvent;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

public class OperSenderImpl implements OperSender, ApplicationListener {

    private static Logger log = Logger.getLogger(OperSenderImpl.class);

    private OperLoader operLoader;

    private SendingFacade sendingFacade;

    private ClientConfig client;

    private ClientLogger clientLogger;

    private OperFilter filter;

    private volatile boolean stop;

    private PlatformTransactionManager transactionManager;

    private ServerPing serverPing;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public SendingFacade getSendingFacade() {
        return sendingFacade;
    }

    public void setSendingFacade(SendingFacade sendingFacade) {
        this.sendingFacade = sendingFacade;
    }

    public void execute() {
        clientLogger.infoLocal("Oper sending, pinging server");
        if (!serverPing.ping()) {
            clientLogger.errorLocal("Skip oper sending, server ping failed");
            return;
        }
        clientLogger.infoAll("Starting to send opers");
        client.reloadSettings();
        try {
            int count = 0;
            while (!stop && count < client.getSettings().getMaxOpersPerSession()) {
                List<OperDTO> opers = loadOpersInTx();
                if (opers.isEmpty())
                    break;
                count += opers.size();
                filter.execute(opers);
                sendingFacade.storeOpers(client.getServerName(), opers);
                deleteOpersInTx(opers);
                clientLogger.infoAll("Sent " + opers.size() + " opers, total " + count);
            }
            clientLogger.infoAll("Finished sending opers, total " + count);
        } catch (Exception e) {
            clientLogger.errorAll("Oper sending failed", e);
        }
    }

    @SuppressWarnings("unchecked")
    private List<OperDTO> loadOpersInTx() {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        return (List<OperDTO>) transactionTemplate.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus arg0) {
                return operLoader.loadOpers(client.getSettings().getOpersBatchSize());
            }
        });
    }

    private void deleteOpersInTx(final List<OperDTO> opers) {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                operLoader.delete(opers);
            }
        });
    }

    public void setClient(ClientConfig client) {
        this.client = client;
    }

    public void setClientLogger(ClientLogger clientLogger) {
        this.clientLogger = clientLogger;
    }

    public void setFilter(OperFilter filter) {
        this.filter = filter;
    }

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ShutdownEvent) {
            log.info("Retreived shutdown event");
            stop = true;
        }
    }

    public void setOperLoader(OperLoader operLoader) {
        this.operLoader = operLoader;
    }

    public void setServerPing(ServerPing serverPing) {
        this.serverPing = serverPing;
    }
}
